package com.googlecode.connectlet.crypto.stream.provider;

import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.Key;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidParameterSpecException;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;

import com.googlecode.connectlet.crypto.EasyProvider;
import com.googlecode.connectlet.crypto.cipher.CipherFactory;
import com.googlecode.connectlet.crypto.stream.FeedbackCipher;
import com.googlecode.connectlet.crypto.stream.StreamCipher;

public abstract class FeedbackCipherSpi extends StreamCipherSpi {
	protected abstract FeedbackCipher getEncCipher(CipherFactory factory,
			byte[] key, byte[] iv);
	protected abstract FeedbackCipher getDecCipher(CipherFactory factory,
			byte[] key, byte[] iv);

	private CipherFactory cipherFactory;
	private IvParameterSpec paramSpec;

	@Deprecated
	@Override
	protected StreamCipher getCipher(byte[] key) {
		throw new UnsupportedOperationException();
	}

	protected FeedbackCipherSpi(CipherFactory cipherFactory) {
		this.cipherFactory = cipherFactory;
	}

	private void init(int opmode, byte[] key, byte[] iv) {
		if (opmode == Cipher.ENCRYPT_MODE) {
			cipher = getEncCipher(cipherFactory, key, iv);
		} else {
			cipher = getDecCipher(cipherFactory, key, iv);
		}
	}

	@Override
	protected void engineInit(int opmode, Key key, SecureRandom random) {
		byte[] iv = new byte[cipherFactory.getBlockSize()];
		random.nextBytes(iv);
		paramSpec = new IvParameterSpec(iv);
		init(opmode, key.getEncoded(), iv);
	}

	@Override
	protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
			SecureRandom random) throws InvalidAlgorithmParameterException {
		if (params instanceof IvParameterSpec) {
			paramSpec = (IvParameterSpec) params;
		} else {
			throw new InvalidAlgorithmParameterException("IvParameterSpec required");
		}
		init(opmode, key.getEncoded(), paramSpec.getIV());
	}

	@Override
	protected void engineInit(int opmode, Key key, AlgorithmParameters params,
			SecureRandom random) throws InvalidAlgorithmParameterException {
		try {
			paramSpec = params.getParameterSpec(IvParameterSpec.class);
		} catch (InvalidParameterSpecException e) {
			throw new InvalidAlgorithmParameterException(e);
		}
		init(opmode, key.getEncoded(), paramSpec.getIV());
	}

	@Override
	protected byte[] engineGetIV() {
		return paramSpec.getIV();
	}

	@Override
	protected AlgorithmParameters engineGetParameters() {
		try {
			AlgorithmParameters result = AlgorithmParameters.
					getInstance("IV", EasyProvider.getInstance());
			result.init(paramSpec);
			return result;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}